<!-- npm install fabric -->

<template>
  <div class="app-container">
    <el-card>
      <div class="main-content">
        <div class="canvas-section">
          <div class="toolbar">
            <el-button @click="handleUpload" type="primary" size="large">
              <el-icon>
                <Upload />
              </el-icon>
              上传图片
            </el-button>
            <input ref="fileInput" type="file" accept="image/*" style="display: none" @change="onFileChanged" />

            <el-button-group class="action-buttons">
              <!-- 缩放控制 -->
              <el-button @click="zoomIn" type="primary" circle>
                <el-icon>
                  <ZoomIn />
                </el-icon>
              </el-button>
              <el-button @click="zoomOut" type="primary" circle>
                <el-icon>
                  <ZoomOut />
                </el-icon>
              </el-button>
              <el-button @click="resetView" type="info" circle>
                <el-icon>
                  <Refresh />
                </el-icon>
              </el-button>

              <!-- 旋转控制 -->
              <el-button @click="rotateLeft" type="primary" circle>
                <el-icon>
                  <RefreshLeft />
                </el-icon>
              </el-button>
              <el-button @click="rotateRight" type="primary" circle>
                <el-icon>
                  <RefreshRight />
                </el-icon>
              </el-button>

              <!-- 裁剪控制 -->
              <el-button @click="startCropping" type="success" :disabled="!hasImage">
                <el-icon>
                  <Crop />
                </el-icon>
                裁剪
              </el-button>
              <el-button @click="autoCrop" type="primary" :disabled="!hasImage">
                <el-icon>
                  <Scissor />
                </el-icon>
                裁边
              </el-button>
              <el-button @click="toggleSpotRemoval" :type="isSpotRemovalMode ? 'danger' : 'primary'" :disabled="!hasImage">
                <el-icon>
                  <Brush />
                </el-icon>
                {{ isSpotRemovalMode ? "退出修复" : "去污修复" }}
              </el-button>
              <el-button @click="openStitchDialog" type="primary">
                <el-icon>
                  <CopyDocument />
                </el-icon>
                图片拼接
              </el-button>
              <el-button @click="openDeskewBar" type="primary" :disabled="!hasImage">
                <el-icon>
                  <Refresh />
                </el-icon>
                纠斜
              </el-button>
              <el-button @click="openFilterBar" type="primary" :disabled="!hasImage">
                <el-icon>
                  <MagicStick />
                </el-icon>
                滤镜
              </el-button>
              <el-button @click="saveCroppedImage" type="warning" :disabled="!hasImage">
                <el-icon>
                  <Download />
                </el-icon>
                保存
              </el-button>
              <el-button @click="showTextBar = true" type="primary" :disabled="!hasImage">
                <el-icon><i class="el-icon-edit"></i></el-icon> 文字
              </el-button>
            </el-button-group>
          </div>

          <div class="canvas-container">
            <canvas ref="canvas" width="1000" height="600"></canvas>

            <!-- 裁剪框元素 -->
            <div
              ref="cropBox"
              class="crop-box"
              v-if="isCropping"
              :style="{
                width: `${cropSize.width}px`,
                height: `${cropSize.height}px`,
                left: `${cropPosition.x}px`,
                top: `${cropPosition.y}px`
              }"
              @mousedown="moveCropBox($event)"
            >
              <!-- 确认裁剪按钮（勾符号） -->
              <el-button class="crop-confirm-btn" type="success" circle size="small" @click.stop="performCrop">
                <el-icon>
                  <Check />
                </el-icon>
              </el-button>
              <div class="crop-handle top-left" @mousedown="startResize($event, 'top-left')"></div>
              <div class="crop-handle top-right" @mousedown="startResize($event, 'top-right')"></div>
              <div class="crop-handle bottom-left" @mousedown="startResize($event, 'bottom-left')"></div>
              <div class="crop-handle bottom-right" @mousedown="startResize($event, 'bottom-right')"></div>
            </div>

            <!-- 污点标记 -->
            <div
              v-for="(spot, index) in spots"
              :key="index"
              class="spot-marker"
              :style="{ left: `${spot.x}px`, top: `${spot.y}px` }"
            ></div>

            <div class="canvas-overlay">
              缩放: {{ (scale * 100).toFixed(0) }}% | X: {{ offsetX.toFixed(0) }} | Y: {{ offsetY.toFixed(0) }} | 旋转:
              {{ rotation }}°
              <span v-if="isCropping"> | 裁剪区域: {{ cropSize.width }}×{{ cropSize.height }}px</span>
            </div>

            <div class="rotation-indicator" v-if="hasImage">
              <el-progress type="dashboard" :percentage="rotationPercentage" :color="rotationProgressColors">
                <template #default>
                  <div class="rotation-angle">{{ rotation }}°</div>
                </template>
              </el-progress>
            </div>

            <!-- 撤销操作提示 -->
            <div class="undo-hint" v-if="isCropping">
              <el-alert title="按ESC键取消当前裁剪操作" type="info" :closable="false" />
            </div>

            <!-- 操作说明 -->
            <div class="instructions" v-if="showInstructions">
              <el-card shadow="hover">
                <template #header>
                  <span>操作说明</span>
                  <el-button style="float: right; padding: 3px 0" type="text" @click="showInstructions = false">
                    <el-icon>
                      <Close />
                    </el-icon>
                  </el-button>
                </template>
                <div class="instructions-scroll" style="max-height: 350px; overflow-y: auto">
                  <div class="instruction-item">
                    <h3>
                      <el-icon>
                        <Crop />
                      </el-icon>
                      裁剪功能
                    </h3>
                    <p>1. 点击"裁剪"按钮进入裁剪模式</p>
                    <p>2. 拖拽裁剪框边缘调整大小</p>
                    <p>3. 拖拽裁剪框内部移动位置</p>
                    <p>4. 点击"保存裁剪"导出结果</p>
                    <p>5. 按ESC键取消当前裁剪操作</p>
                  </div>
                  <div class="instruction-item">
                    <h3>
                      <el-icon>
                        <Scissor />
                      </el-icon>
                      裁边功能
                    </h3>
                    <p>1. 点击"裁边"按钮自动检测并裁剪空白边缘</p>
                    <p>2. 使用滑块调整边缘检测灵敏度</p>
                  </div>
                  <div class="instruction-item">
                    <h3>
                      <el-icon>
                        <Brush />
                      </el-icon>
                      去污修复
                    </h3>
                    <p>1. 点击"去污修复"按钮进入修复模式</p>
                    <p>2. 点击图片上的污点进行修复</p>
                    <p>3. 使用滑块调整修复半径</p>
                    <p>4. 再次点击按钮退出修复模式</p>
                  </div>
                  <div class="instruction-item">
                    <h3>
                      <el-icon>
                        <CopyDocument />
                      </el-icon>
                      图片拼接
                    </h3>
                    <p>1. 点击"图片拼接"按钮添加多张图片</p>
                    <p>2. 在图片堆栈中管理要拼接的图片</p>
                    <p>3. 点击"执行拼接"生成拼接结果</p>
                  </div>
                </div>
              </el-card>
            </div>
          </div>
        </div>

        <div class="preview-section">
          <el-divider>预览区域</el-divider>

          <div class="preview-container">
            <div class="preview-placeholder" v-if="!hasImage">
              <el-empty description="请上传图片" />
            </div>

            <div class="original-preview" v-if="hasImage">
              <h3>原始图片</h3>
              <img :src="originalImageSrc" class="preview-image" />
              <div class="image-info">尺寸: {{ imageSize.width }}×{{ imageSize.height }}px</div>
            </div>

            <div class="cropped-preview" v-if="hasImage && isCropped">
              <h3>裁剪结果</h3>
              <img :src="croppedImageSrc" class="preview-image" />
              <div class="image-info">尺寸: {{ cropSize.width }}×{{ cropSize.height }}px</div>
            </div>
          </div>

          <!-- 图片拼接堆栈 -->
          <div class="image-stack" v-if="imageStack.length > 0">
            <h3>图片拼接堆栈</h3>
            <div class="stack-container">
              <div class="stack-item" v-for="(img, index) in imageStack" :key="index">
                <img :src="img.src" class="stack-thumb" />
                <div class="stack-info">
                  <div>{{ img.name }}</div>
                  <div class="image-info">尺寸: {{ img.width }}×{{ img.height }}px</div>
                </div>
                <div class="stack-actions">
                  <el-button @click="removeFromStack(index)" type="danger" circle>
                    <el-icon>
                      <Close />
                    </el-icon>
                  </el-button>
                </div>
              </div>
            </div>
            <el-button @click="stitchImages" type="success" :disabled="imageStack.length < 2">
              <el-icon>
                <Connection />
              </el-icon>
              执行拼接
            </el-button>
          </div>
        </div>
      </div>
    </el-card>

    <!-- 保存确认对话框 -->
    <el-dialog v-model="saveDialogVisible" title="保存裁剪图片" width="500px">
      <el-form label-position="top">
        <el-form-item label="图片名称">
          <el-input v-model="saveName" placeholder="输入保存名称" />
        </el-form-item>
        <el-form-item label="图片格式">
          <el-select v-model="saveFormat" class="m-2" placeholder="Select">
            <el-option label="PNG" value="image/png" />
            <el-option label="JPEG" value="image/jpeg" />
            <el-option label="WEBP" value="image/webp" />
          </el-select>
        </el-form-item>
      </el-form>

      <template #footer>
        <el-button @click="saveDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="downloadImage">确认保存</el-button>
      </template>
    </el-dialog>

    <!-- 图片拼接对话框 -->
    <el-dialog v-model="stitchDialogVisible" title="图片拼接" width="600px">
      <el-upload class="upload-demo" drag multiple :on-change="handleStitchUpload" :auto-upload="false" :show-file-list="false">
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">拖拽图片到此处或 <em>点击上传</em></div>
        <template #tip>
          <div class="el-upload__tip">上传多张图片进行拼接</div>
        </template>
      </el-upload>

      <div class="stitch-preview" v-if="imageStack.length > 0">
        <h3>已选图片 ({{ imageStack.length }})</h3>
        <div class="preview-grid">
          <div class="preview-item" v-for="(img, index) in imageStack" :key="index">
            <img :src="img.src" class="preview-thumb" />
            <div class="preview-name">{{ img.name }}</div>
            <el-button @click="removeFromStack(index)" type="danger" circle size="small">
              <el-icon>
                <Close />
              </el-icon>
            </el-button>
          </div>
        </div>
      </div>

      <template #footer>
        <el-button @click="stitchDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="stitchImages" :disabled="imageStack.length < 2">执行拼接</el-button>
      </template>
    </el-dialog>

    <!-- 滤镜工具栏 -->
    <el-drawer title="调整图片属性" v-model="showFilterBar" size="400px">
      <div class="filter-bar">
        <el-form label-position="top">
          <el-form-item label="亮度">
            <el-slider v-model="filterState.brightness" :min="0" :max="200" show-input @input="applyFilter" />
          </el-form-item>
          <el-form-item label="对比度">
            <el-slider v-model="filterState.contrast" :min="0" :max="200" show-input @input="applyFilter" />
          </el-form-item>
          <el-form-item label="饱和度">
            <el-slider v-model="filterState.saturate" :min="0" :max="200" show-input @input="applyFilter" />
          </el-form-item>
          <el-form-item label="灰度">
            <el-slider v-model="filterState.grayscale" :min="0" :max="100" show-input @input="applyFilter" />
          </el-form-item>
          <el-form-item label="褐色">
            <el-slider v-model="filterState.sepia" :min="0" :max="100" show-input @input="applyFilter" />
          </el-form-item>
        </el-form>

        <div class="filter-actions">
          <el-button @click="closeFilterBar">完成</el-button>
          <el-button @click="resetFilter" type="warning">重置</el-button>
        </div>
      </div>
    </el-drawer>

    <!-- 纠斜工具栏 -->
    <el-drawer title="纠斜调整" v-model="showDeskewBar" size="400px">
      <div class="deskew-bar">
        <el-form label-position="top">
          <el-form-item label="手动旋转">
            <el-slider v-model="deskewAngle" :min="-45" :max="45" :step="0.1" show-input @input="applyDeskewManual" />
          </el-form-item>
        </el-form>
        <div class="deskew-actions">
          <el-button @click="autoDeskew">自动纠斜</el-button>
          <el-button @click="resetDeskew" type="warning">重置</el-button>
          <el-button @click="closeDeskewBar" type="primary">完成</el-button>
        </div>
      </div>
    </el-drawer>

    <!-- 文字工具栏 -->
    <el-drawer title="添加文字" v-model="showTextBar" size="400px">
      <div class="text-bar">
        <el-form label-position="top">
          <el-form-item label="输入文字">
            <el-input v-model="textInput" placeholder="请输入要添加的文字" />
          </el-form-item>
          <el-form-item label="字体">
            <el-select v-model="textFont" style="width: 100%">
              <el-option v-for="font in fontOptions" :key="font" :label="font" :value="font" />
            </el-select>
          </el-form-item>
          <el-form-item label="字号">
            <el-select v-model="textFontSize" style="width: 100%">
              <el-option v-for="size in fontSizeOptions" :key="size" :label="size + 'px'" :value="size" />
            </el-select>
          </el-form-item>
          <el-form-item label="颜色">
            <el-color-picker v-model="textColor" />
          </el-form-item>
          <el-form-item label="样式">
            <el-checkbox v-model="textBold">加粗</el-checkbox>
            <el-checkbox v-model="textItalic">斜体</el-checkbox>
            <el-checkbox v-model="textUnderline">下划线</el-checkbox>
          </el-form-item>
          <el-form-item label="位置">
            <el-input-number v-model="textPos.x" :min="0" :max="canvas?.value?.width || 1000" label="X" />
            <el-input-number
              v-model="textPos.y"
              :min="0"
              :max="canvas?.value?.height || 600"
              label="Y"
              style="margin-left: 10px"
            />
          </el-form-item>
        </el-form>
        <div class="text-actions" style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px">
          <el-button @click="addTextToImage" type="primary" :disabled="!textInput">添加文字</el-button>
          <el-button @click="resetTextInput">重置</el-button>
          <el-button @click="closeTextBar">完成</el-button>
        </div>
        <div style="margin-top: 10px; font-size: 13px; color: #5cbfff">可在画布上拖动实时预览文字定位</div>
      </div>
    </el-drawer>
  </div>
</template>

<script setup>
import { ref, onMounted, computed } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
  Upload,
  ZoomIn,
  ZoomOut,
  Refresh,
  Crop,
  Download,
  RefreshLeft,
  RefreshRight,
  Close,
  Scissor,
  Brush,
  CopyDocument,
  Connection,
  UploadFilled,
  MagicStick
} from "@element-plus/icons-vue";

// Canvas元素引用
const canvas = ref(null);
const ctx = ref(null);

// 图片状态
const originalImageSrc = ref("");
const croppedImageSrc = ref("");
const imageSize = ref({ width: 0, height: 0 });
const hasImage = ref(false);
const isCropped = ref(false);
const rotation = ref(0); // 当前旋转角度（0-360度）

// Canvas视图状态
const scale = ref(1.0);
const offsetX = ref(0);
const offsetY = ref(0);
const isDragging = ref(false);
const startX = ref(0);
const startY = ref(0);

// 裁剪相关状态
const cropBox = ref(null);
const cropPosition = ref({ x: 100, y: 100 });
const cropSize = ref({ width: 300, height: 200 });
const cropStartPoint = ref({ x: 0, y: 0 });
const isCropping = ref(false);
const isResizing = ref(false);
const resizeDirection = ref("");
const fileInput = ref(null);

// 保存相关
const saveDialogVisible = ref(false);
const saveName = ref("cropped-image");
const saveFormat = ref("image/png");

// 新增功能状态
const isSpotRemovalMode = ref(false);
const spots = ref([]);
const spotRadius = ref(15);
const edgeSensitivity = ref(80); // 裁边灵敏度
const imageStack = ref([]); // 图片拼接堆栈
const stitchDialogVisible = ref(false);
const showInstructions = ref(true); // 操作说明显示状态
const showFilterBar = ref(false); // 滤镜工具栏显示状态
const showDeskewBar = ref(false); // 纠斜工具栏显示状态
const showTextBar = ref(false); // 文字工具栏显示状态
const textInput = ref("");
const textFont = ref("Arial");
const textFontSize = ref(36);
const textColor = ref("#ffffff");
const textBold = ref(false);
const textItalic = ref(false);
const textUnderline = ref(false);
const textPos = ref({ x: 200, y: 200 });
const textPreviewDragging = ref(false);
const textPreviewOffset = ref({ x: 0, y: 0 });
const textPreviewBox = ref(null);

const fontOptions = ["Arial", "Microsoft YaHei", "SimSun", "SimHei", "Times New Roman", "Courier New", "Verdana", "Tahoma"];
const fontSizeOptions = [16, 24, 32, 36, 48, 56, 64, 72];

// 旋转进度指示器
const rotationProgressColors = [
  { color: "#f56c6c", percentage: 20 },
  { color: "#e6a23c", percentage: 40 },
  { color: "#5cb87a", percentage: 60 },
  { color: "#1989fa", percentage: 80 },
  { color: "#6f7ad3", percentage: 100 }
];

// 滤镜状态
const filterState = ref({
  brightness: 100,
  contrast: 100,
  saturate: 100,
  grayscale: 0,
  sepia: 0
});

// 纠斜相关状态
const deskewAngle = ref(0); // 手动旋转角度
const autoDeskewEnabled = ref(false); // 自动纠斜开关

// 计算旋转百分比
const rotationPercentage = computed(() => {
  return Math.round((rotation.value / 360) * 100);
});

// 初始化Canvas
onMounted(() => {
  const canvasEl = canvas.value;
  if (canvasEl) {
    ctx.value = canvasEl.getContext("2d");
    addEventListeners();
    drawGrid();
  }
});

// 添加事件监听
const addEventListeners = () => {
  if (!canvas.value) return;

  canvas.value.addEventListener("wheel", handleWheel);
  canvas.value.addEventListener("mousedown", startDrag);
  canvas.value.addEventListener("click", handleCanvasClick);
  document.addEventListener("mousemove", drag);
  document.addEventListener("mouseup", endDrag);
  document.addEventListener("keydown", handleKeyDown);
};

// 键盘快捷键
const handleKeyDown = e => {
  if (e.code === "Space") resetView();
  if (e.code === "Equal" || e.code === "NumpadAdd") zoomIn();
  if (e.code === "Minus" || e.code === "NumpadSubtract") zoomOut();
  if (e.code === "KeyC" && hasImage.value) startCropping();
  if (e.code === "KeyR" && hasImage.value) rotateRight();
  if (e.code === "KeyL" && hasImage.value) rotateLeft();

  // ESC键取消裁剪操作
  if (e.code === "Escape") {
    if (isCropping.value) cancelCropping();
    if (isSpotRemovalMode.value) toggleSpotRemoval();
  }

  // I键显示/隐藏操作说明
  if (e.code === "KeyI") showInstructions.value = !showInstructions.value;
};

// 旋转功能实现
const rotateLeft = () => {
  rotation.value = (rotation.value - 90 + 360) % 360;
  drawImage();
  ElMessage.success(`图片已逆时针旋转90度 (当前角度: ${rotation.value}°)`);
};

const rotateRight = () => {
  rotation.value = (rotation.value + 90) % 360;
  drawImage();
  ElMessage.success(`图片已顺时针旋转90度 (当前角度: ${rotation.value}°)`);
};

// 重置旋转角度
const resetRotation = () => {
  rotation.value = 0;
};

// 鼠标滚轮缩放
const handleWheel = e => {
  e.preventDefault();

  const rect = canvas.value.getBoundingClientRect();
  const mouseX = e.clientX - rect.left;
  const mouseY = e.clientY - rect.top;

  const zoomIntensity = 0.05;
  const wheelDelta = e.deltaY < 0 ? 1 : -1;
  const newScale = Math.min(4, Math.max(0.25, scale.value * (1 + wheelDelta * zoomIntensity)));

  scale.value = newScale;
  drawImage();
};

// 拖拽事件处理
const startDrag = e => {
  if (isResizing.value || (isCropping.value && e.target === cropBox.value)) return;

  isDragging.value = true;
  startX.value = e.clientX;
  startY.value = e.clientY;
};

const drag = e => {
  if (!isDragging.value) {
    if (isResizing.value) {
      handleResize(e);
    }
    return;
  }

  const dx = (e.clientX - startX.value) / scale.value;
  const dy = (e.clientY - startY.value) / scale.value;

  offsetX.value -= dx;
  offsetY.value -= dy;

  startX.value = e.clientX;
  startY.value = e.clientY;

  drawImage();
};

const endDrag = () => {
  isDragging.value = false;
  isResizing.value = false;
};

// 缩放控制
const zoomIn = () => {
  scale.value = Math.min(4, scale.value * 1.2);
  drawImage();
};

const zoomOut = () => {
  scale.value = Math.max(0.25, scale.value * 0.8);
  drawImage();
};

const resetView = () => {
  scale.value = 1;
  offsetX.value = 0;
  offsetY.value = 0;
  resetRotation();
  drawImage();
  ElMessage.success("视图已重置");
};

// 文件上传处理
const handleUpload = () => {
  if (fileInput.value) {
    fileInput.value.click();
  }
};

const onFileChanged = e => {
  const input = e.target;
  if (!input.files || input.files.length === 0) return;

  const file = input.files[0];
  const reader = new FileReader();

  reader.onload = event => {
    if (typeof event.target?.result === "string") {
      originalImageSrc.value = event.target.result;

      const img = new Image();
      img.onload = () => {
        imageSize.value = {
          width: img.width,
          height: img.height
        };

        // 重置视图并显示图片
        resetView();
        resetRotation();
        hasImage.value = true;
        isCropped.value = false;

        // 默认设置裁剪区域为图片中央区域
        const cropWidth = Math.min(300, img.width * 0.6);
        const cropHeight = Math.min(200, img.height * 0.6);
        cropPosition.value = {
          x: (1000 - cropWidth) / 2,
          y: (600 - cropHeight) / 2
        };
        cropSize.value = {
          width: cropWidth,
          height: cropHeight
        };

        // 绘制图片
        drawImage();
      };
      img.src = originalImageSrc.value;
    }
  };

  reader.readAsDataURL(file);
  if (input) input.value = ""; // 重置文件输入，允许选择相同文件
};

// 绘制网格背景
const drawGrid = () => {
  if (!ctx.value || !canvas.value) return;

  ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);

  ctx.value.strokeStyle = "rgba(100, 200, 255, 0.1)";
  ctx.value.lineWidth = 1;

  const step = 50;
  const width = canvas.value.width;
  const height = canvas.value.height;

  // 垂直线
  for (let x = 0; x <= width; x += step) {
    ctx.value.beginPath();
    ctx.value.moveTo(x, 0);
    ctx.value.lineTo(x, height);
    ctx.value.stroke();
  }

  // 水平线
  for (let y = 0; y <= height; y += step) {
    ctx.value.beginPath();
    ctx.value.moveTo(0, y);
    ctx.value.lineTo(width, y);
    ctx.value.stroke();
  }
};

// 绘制上传的图片（含旋转处理）
const drawImage = () => {
  if (!ctx.value || !canvas.value) return;
  if (!hasImage.value) {
    drawGrid();
    return;
  }

  ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);

  const img = new Image();
  img.onload = () => {
    const canvasEl = canvas.value;

    // 保存当前状态
    ctx.value.save();

    // 应用缩放和平移
    ctx.value.translate(offsetX.value * scale.value, offsetY.value * scale.value);
    ctx.value.scale(scale.value, scale.value);

    // 获取旋转后的中心点
    const centerX = canvasEl.width / (2 * scale.value) - offsetX.value;
    const centerY = canvasEl.height / (2 * scale.value) - offsetY.value;

    // 应用旋转
    if (rotation.value !== 0) {
      const radians = (rotation.value * Math.PI) / 180;
      ctx.value.translate(centerX, centerY);
      ctx.value.rotate(radians);
      ctx.value.translate(-centerX, -centerY);
    }

    // 绘制图片
    ctx.value.drawImage(img, 0, 0, img.width, img.height);

    // 恢复状态
    ctx.value.restore();
  };

  img.src = originalImageSrc.value;
};

// 开始裁剪
const startCropping = () => {
  isCropping.value = true;
  ElMessage.success("拖拽裁剪框边缘可调整大小，拖拽框内可移动位置");
};

// 移动裁剪框
const moveCropBox = e => {
  if (!isCropping.value || !cropBox.value || isResizing.value) return;

  const rect = canvas.value.getBoundingClientRect();
  cropPosition.value = {
    x: e.clientX - rect.left - cropSize.value.width / 2,
    y: e.clientY - rect.top - cropSize.value.height / 2
  };

  // 限制在Canvas范围内
  cropPosition.value.x = Math.max(0, Math.min(rect.width - cropSize.value.width, cropPosition.value.x));
  cropPosition.value.y = Math.max(0, Math.min(rect.height - cropSize.value.height, cropPosition.value.y));
};

// 调整裁剪框大小
const startResize = (e, direction) => {
  e.stopPropagation();
  isResizing.value = true;
  resizeDirection.value = direction;
  cropStartPoint.value = {
    x: e.clientX,
    y: e.clientY
  };

  document.addEventListener("mousemove", handleResize);
  document.addEventListener("mouseup", () => {
    isResizing.value = false;
    document.removeEventListener("mousemove", handleResize);
  });
};

const handleResize = e => {
  if (!isResizing.value || !cropBox.value) return;

  const dx = e.clientX - cropStartPoint.value.x;
  const dy = e.clientY - cropStartPoint.value.y;

  // 更新裁剪框尺寸
  switch (resizeDirection.value) {
    case "top-left":
      cropSize.value.width -= dx;
      cropSize.value.height -= dy;
      cropPosition.value.x += dx;
      cropPosition.value.y += dy;
      break;
    case "top-right":
      cropSize.value.width += dx;
      cropSize.value.height -= dy;
      cropPosition.value.y += dy;
      break;
    case "bottom-left":
      cropSize.value.width -= dx;
      cropSize.value.height += dy;
      cropPosition.value.x += dx;
      break;
    case "bottom-right":
      cropSize.value.width += dx;
      cropSize.value.height += dy;
      break;
  }

  // 确保最小尺寸
  cropSize.value.width = Math.max(50, cropSize.value.width);
  cropSize.value.height = Math.max(50, cropSize.value.height);

  // 更新开始位置
  cropStartPoint.value = {
    x: e.clientX,
    y: e.clientY
  };

  // 限制位置在Canvas范围内
  const rect = canvas.value.getBoundingClientRect();
  cropPosition.value.x = Math.max(0, Math.min(rect.width - cropSize.value.width, cropPosition.value.x));
  cropPosition.value.y = Math.max(0, Math.min(rect.height - cropSize.value.height, cropPosition.value.y));
};

// 执行图片裁剪（含旋转处理）
const performCrop = () => {
  if (!ctx.value || !canvas.value || !hasImage.value) return;

  const img = new Image();
  img.onload = () => {
    // 创建临时Canvas用于裁剪
    const tempCanvas = document.createElement("canvas");
    const tempCtx = tempCanvas.getContext("2d");

    // 计算旋转后的实际尺寸
    if (rotation.value % 180 !== 0) {
      tempCanvas.width = img.height;
      tempCanvas.height = img.width;
    } else {
      tempCanvas.width = img.width;
      tempCanvas.height = img.height;
    }

    // 旋转并绘制图片到临时Canvas
    tempCtx.save();

    // 移动到中心
    tempCtx.translate(tempCanvas.width / 2, tempCanvas.height / 2);

    // 应用旋转
    tempCtx.rotate((rotation.value * Math.PI) / 180);

    // 根据旋转角度调整绘制位置
    if (rotation.value % 180 === 0) {
      tempCtx.drawImage(img, -img.width / 2, -img.height / 2);
    } else {
      tempCtx.drawImage(img, -img.height / 2, -img.width / 2);
    }

    tempCtx.restore();

    // 计算裁剪区域（现在在旋转后的坐标系中）
    const canvasRect = canvas.value.getBoundingClientRect();
    let scaleFactor = img.width / canvasRect.width;

    // 调整坐标系
    let cropX = cropPosition.value.x;
    let cropY = cropPosition.value.y;

    // 根据旋转角度调整坐标系
    switch (rotation.value) {
      case 90:
        cropX = canvasRect.height - (cropPosition.value.y + cropSize.value.height);
        cropY = cropPosition.value.x;
        scaleFactor = img.height / canvasRect.height;
        break;
      case 180:
        cropX = canvasRect.width - (cropPosition.value.x + cropSize.value.width);
        cropY = canvasRect.height - (cropPosition.value.y + cropSize.value.height);
        break;
      case 270:
        cropX = cropPosition.value.y;
        cropY = canvasRect.width - (cropPosition.value.x + cropSize.value.width);
        scaleFactor = img.height / canvasRect.width;
        break;
    }

    // 计算裁剪区域在原始图片上的位置（考虑缩放）
    cropX *= scaleFactor;
    cropY *= scaleFactor;
    const cropWidth = cropSize.value.width * scaleFactor;
    const cropHeight = cropSize.value.height * scaleFactor;

    // 创建最终裁剪Canvas
    const cropCanvas = document.createElement("canvas");
    cropCanvas.width = cropWidth;
    cropCanvas.height = cropHeight;
    const cropCtx = cropCanvas.getContext("2d");

    // 从旋转后的图片中裁剪所需部分
    cropCtx.drawImage(tempCanvas, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);

    // 获取裁剪后的图片数据URL
    croppedImageSrc.value = cropCanvas.toDataURL("image/png");
    isCropped.value = true;
    isCropping.value = false;
  };
  // 设置图片源
  img.src = originalImageSrc.value;
};

// 保存裁剪图片
const saveCroppedImage = () => {
  // 确保已经裁剪过
  if (isCropping.value) {
    performCrop();
  }

  if (isCropped.value) {
    saveDialogVisible.value = true;
  }
};

// 下载裁剪后的图片
const downloadImage = () => {
  if (!croppedImageSrc.value || !isCropped.value) return;

  const link = document.createElement("a");
  const fileName = saveName.value || "cropped-image";
  const extension = saveFormat.value.split("/")[1] || "png";

  link.download = `${fileName}.${extension}`;
  link.href = croppedImageSrc.value.replace("image/png", saveFormat.value);

  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);

  saveDialogVisible.value = false;
  ElMessage.success("图片保存成功");
};

// 取消裁剪操作
const cancelCropping = () => {
  isCropping.value = false;
  ElMessage.info("已取消裁剪操作");
};

// ===================== 新增功能实现 =====================

// 1. 智能裁边功能
const autoCrop = () => {
  if (!hasImage.value) return;

  ElMessage.info("正在智能裁边...");

  const img = new Image();
  img.onload = () => {
    // 创建临时Canvas
    const tempCanvas = document.createElement("canvas");
    tempCanvas.width = img.width;
    tempCanvas.height = img.height;
    const tempCtx = tempCanvas.getContext("2d");
    tempCtx.drawImage(img, 0, 0);

    const imageData = tempCtx.getImageData(0, 0, img.width, img.height);
    const data = imageData.data;

    // 边缘检测阈值（基于灵敏度设置）
    const threshold = 255 * (edgeSensitivity.value / 100);

    // 查找边缘位置
    let top = img.height,
      bottom = 0,
      left = img.width,
      right = 0;

    for (let y = 0; y < img.height; y++) {
      for (let x = 0; x < img.width; x++) {
        const idx = (y * img.width + x) * 4;
        const r = data[idx];
        const g = data[idx + 1];
        const b = data[idx + 2];

        // 计算像素亮度
        const brightness = (r + g + b) / 3;

        // 如果像素亮度低于阈值，则认为是内容区域
        if (brightness < threshold) {
          if (y < top) top = y;
          if (y > bottom) bottom = y;
          if (x < left) left = x;
          if (x > right) right = x;
        }
      }
    }

    // 确保找到有效区域
    if (top < bottom && left < right) {
      // 计算裁剪尺寸
      const cropWidth = right - left + 1;
      const cropHeight = bottom - top + 1;

      // 创建裁剪后的Canvas
      const cropCanvas = document.createElement("canvas");
      cropCanvas.width = cropWidth;
      cropCanvas.height = cropHeight;
      const cropCtx = cropCanvas.getContext("2d");

      // 裁剪图片
      cropCtx.drawImage(img, left, top, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);

      // 更新图片
      originalImageSrc.value = cropCanvas.toDataURL("image/png");
      imageSize.value = { width: cropWidth, height: cropHeight };
      drawImage();

      ElMessage.success(`裁边完成，裁剪尺寸: ${cropWidth}×${cropHeight}px`);
    } else {
      ElMessage.warning("未检测到可裁剪的边缘");
    }
  };

  img.src = originalImageSrc.value;
};

// 2. 污点修复功能
const toggleSpotRemoval = () => {
  isSpotRemovalMode.value = !isSpotRemovalMode.value;
  spots.value = [];

  if (isSpotRemovalMode.value) {
    ElMessage.success("进入去污修复模式，点击图片上的污点进行修复");
  } else {
    ElMessage.info("已退出去污修复模式");
  }
};

const handleCanvasClick = e => {
  if (!isSpotRemovalMode.value || !hasImage.value) return;

  const rect = canvas.value.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;

  // 添加污点标记
  spots.value.push({ x, y });

  // 在实际应用中，这里会使用图像修复算法
  // 这里简化处理，用周围像素覆盖污点
  setTimeout(() => {
    repairSpot(x, y);
  }, 500);
};

const repairSpot = (x, y) => {
  if (!ctx.value || !canvas.value) return;

  const radius = spotRadius.value;
  const imageData = ctx.value.getImageData(x - radius, y - radius, radius * 2, radius * 2);

  // 在实际应用中，这里会使用更复杂的修复算法
  // 这里简化处理，用平均颜色填充
  let r = 0,
    g = 0,
    b = 0,
    count = 0;

  for (let i = 0; i < imageData.data.length; i += 4) {
    if (i % 4 === 0) {
      // 跳过中心区域
      const dx = ((i / 4) % (radius * 2)) - radius;
      const dy = Math.floor(i / 4 / (radius * 2)) - radius;
      const distance = Math.sqrt(dx * dx + dy * dy);

      if (distance > radius / 2) {
        r += imageData.data[i];
        g += imageData.data[i + 1];
        b += imageData.data[i + 2];
        count++;
      }
    }
  }

  if (count > 0) {
    const avgR = Math.round(r / count);
    const avgG = Math.round(g / count);
    const avgB = Math.round(b / count);

    // 填充中心区域
    for (let i = 0; i < imageData.data.length; i += 4) {
      const dx = ((i / 4) % (radius * 2)) - radius;
      const dy = Math.floor(i / 4 / (radius * 2)) - radius;
      const distance = Math.sqrt(dx * dx + dy * dy);

      if (distance <= radius / 2) {
        imageData.data[i] = avgR;
        imageData.data[i + 1] = avgG;
        imageData.data[i + 2] = avgB;
      }
    }

    ctx.value.putImageData(imageData, x - radius, y - radius);
  }

  // 移除标记
  spots.value = spots.value.filter(spot => !(Math.abs(spot.x - x) < radius && Math.abs(spot.y - y) < radius));
};

// 3. 图片拼接功能
const openStitchDialog = () => {
  stitchDialogVisible.value = true;
};

const handleStitchUpload = file => {
  const reader = new FileReader();
  reader.onload = event => {
    const img = new Image();
    img.onload = () => {
      imageStack.value.push({
        name: file.name,
        src: event.target.result,
        width: img.width,
        height: img.height
      });
      ElMessage.success(`已添加图片: ${file.name}`);
    };
    img.src = event.target.result;
  };
  reader.readAsDataURL(file.raw);
};

const removeFromStack = index => {
  imageStack.value.splice(index, 1);
};

const stitchImages = () => {
  if (imageStack.value.length < 2) {
    ElMessage.warning("请至少添加两张图片进行拼接");
    return;
  }

  ElMessage.info("正在拼接图片...");

  // 计算拼接后尺寸（水平拼接）
  let totalWidth = 0;
  let maxHeight = 0;

  imageStack.value.forEach(img => {
    totalWidth += img.width;
    if (img.height > maxHeight) maxHeight = img.height;
  });

  // 创建拼接画布
  const stitchCanvas = document.createElement("canvas");
  stitchCanvas.width = totalWidth;
  stitchCanvas.height = maxHeight;
  const stitchCtx = stitchCanvas.getContext("2d");

  // 拼接图片
  let xPos = 0;
  const promises = imageStack.value.map(img => {
    return new Promise(resolve => {
      const imgEl = new Image();
      imgEl.onload = () => {
        stitchCtx.drawImage(imgEl, xPos, 0);
        xPos += imgEl.width;
        resolve();
      };
      imgEl.src = img.src;
    });
  });

  Promise.all(promises).then(() => {
    // 更新图片
    originalImageSrc.value = stitchCanvas.toDataURL("image/png");
    imageSize.value = { width: totalWidth, height: maxHeight };
    hasImage.value = true;
    resetView();
    imageStack.value = [];
    stitchDialogVisible.value = false;

    ElMessage.success("图片拼接完成");
  });
};

/**
 * 纠斜功能：自动检测图片主方向并旋转校正
 * 简单实现：基于灰度边缘检测和霍夫变换估算主方向
 */
const deskewImage = () => {
  if (!hasImage.value) return;

  ElMessage.info("正在自动纠斜...");

  const img = new Image();
  img.onload = () => {
    // 创建灰度图像数据
    const tempCanvas = document.createElement("canvas");
    tempCanvas.width = img.width;
    tempCanvas.height = img.height;
    const tempCtx = tempCanvas.getContext("2d");
    tempCtx.drawImage(img, 0, 0);
    const imageData = tempCtx.getImageData(0, 0, img.width, img.height);
    const data = imageData.data;

    // Sobel算子边缘检测
    const width = img.width;
    const height = img.height;
    const gray = new Uint8ClampedArray(width * height);
    for (let i = 0; i < data.length; i += 4) {
      gray[i / 4] = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
    }

    // Sobel卷积核
    const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
    const sobelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
    const edgeAngles = [];
    for (let y = 1; y < height - 1; y++) {
      for (let x = 1; x < width - 1; x++) {
        let gx = 0,
          gy = 0;
        for (let ky = -1; ky <= 1; ky++) {
          for (let kx = -1; kx <= 1; kx++) {
            const val = gray[(y + ky) * width + (x + kx)];
            gx += val * sobelX[(ky + 1) * 3 + (kx + 1)];
            gy += val * sobelY[(ky + 1) * 3 + (kx + 1)];
          }
        }
        const mag = Math.sqrt(gx * gx + gy * gy);
        if (mag > 100) {
          // 边缘阈值
          let angle = (Math.atan2(gy, gx) * 180) / Math.PI;
          if (angle < -90) angle += 180;
          if (angle > 90) angle -= 180;
          edgeAngles.push(angle);
        }
      }
    }

    if (edgeAngles.length === 0) {
      ElMessage.warning("未检测到明显的倾斜线条，无法纠斜");
      return;
    }

    // 统计主方向
    const bins = new Array(180).fill(0);
    edgeAngles.forEach(a => {
      const idx = Math.round(a) + 90;
      bins[idx]++;
    });
    let maxBin = 0,
      maxIdx = 0;
    for (let i = 0; i < bins.length; i++) {
      if (bins[i] > maxBin) {
        maxBin = bins[i];
        maxIdx = i;
      }
    }
    const skewAngle = maxIdx - 90;

    if (Math.abs(skewAngle) < 1) {
      ElMessage.success("图片已基本正直，无需纠斜");
      return;
    }

    // 旋转校正
    rotation.value = (rotation.value - skewAngle + 360) % 360;
    drawImage();
    ElMessage.success(`已自动纠斜，旋转校正角度: ${-skewAngle.toFixed(2)}°`);
  };
  img.src = originalImageSrc.value;
};

const openDeskewBar = () => {
  showDeskewBar.value = true;
  ElMessage.info('调整滑块以修改旋转角度，完成后点击"完成"');
};
// 滤镜相关功能
const openFilterBar = () => {
  showFilterBar.value = true;
  ElMessage.info('调整滑块以修改图片属性，完成后点击"完成"');
};

const closeFilterBar = () => {
  showFilterBar.value = false;
};

const resetFilter = () => {
  filterState.value = {
    brightness: 100,
    contrast: 100,
    saturate: 100,
    grayscale: 0,
    sepia: 0
  };
  applyFilter();
};

// 滤镜应用方法
const applyFilter = () => {
  if (!ctx.value || !canvas.value || !hasImage.value) return;
  const img = new window.Image();
  img.onload = () => {
    ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
    ctx.value.save();
    // 应用滤镜
    ctx.value.filter =
      `brightness(${filterState.value.brightness}%) ` +
      `contrast(${filterState.value.contrast}%) ` +
      `saturate(${filterState.value.saturate}%) ` +
      `grayscale(${filterState.value.grayscale}%) ` +
      `sepia(${filterState.value.sepia}%)`;
    ctx.value.drawImage(img, 0, 0, img.width, img.height);
    ctx.value.restore();
  };
  img.src = originalImageSrc.value;
};

// 手动纠斜功能实现
const applyDeskewManual = () => {
  if (!ctx.value || !canvas.value || !hasImage.value) return;

  // 更新旋转角度
  rotation.value = deskewAngle.value;
  drawImage();
};

// 自动纠斜功能实现
const autoDeskew = () => {
  if (!hasImage.value) return;

  ElMessage.info("正在自动纠斜...");

  const img = new Image();
  img.onload = () => {
    // 创建灰度图像数据
    const tempCanvas = document.createElement("canvas");
    tempCanvas.width = img.width;
    tempCanvas.height = img.height;
    const tempCtx = tempCanvas.getContext("2d");
    tempCtx.drawImage(img, 0, 0);
    const imageData = tempCtx.getImageData(0, 0, img.width, img.height);
    const data = imageData.data;

    // Sobel算子边缘检测
    const width = img.width;
    const height = img.height;
    const gray = new Uint8ClampedArray(width * height);
    for (let i = 0; i < data.length; i += 4) {
      gray[i / 4] = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
    }

    // Sobel卷积核
    const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
    const sobelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
    const edgeAngles = [];
    for (let y = 1; y < height - 1; y++) {
      for (let x = 1; x < width - 1; x++) {
        let gx = 0,
          gy = 0;
        for (let ky = -1; ky <= 1; ky++) {
          for (let kx = -1; kx <= 1; kx++) {
            const val = gray[(y + ky) * width + (x + kx)];
            gx += val * sobelX[(ky + 1) * 3 + (kx + 1)];
            gy += val * sobelY[(ky + 1) * 3 + (kx + 1)];
          }
        }
        const mag = Math.sqrt(gx * gx + gy * gy);
        if (mag > 100) {
          // 边缘阈值
          let angle = (Math.atan2(gy, gx) * 180) / Math.PI;
          if (angle < -90) angle += 180;
          if (angle > 90) angle -= 180;
          edgeAngles.push(angle);
        }
      }
    }

    if (edgeAngles.length === 0) {
      ElMessage.warning("未检测到明显的倾斜线条，无法纠斜");
      return;
    }

    // 统计主方向
    const bins = new Array(180).fill(0);
    edgeAngles.forEach(a => {
      const idx = Math.round(a) + 90;
      bins[idx]++;
    });
    let maxBin = 0,
      maxIdx = 0;
    for (let i = 0; i < bins.length; i++) {
      if (bins[i] > maxBin) {
        maxBin = bins[i];
        maxIdx = i;
      }
    }
    const skewAngle = maxIdx - 90;

    if (Math.abs(skewAngle) < 1) {
      ElMessage.success("图片已基本正直，无需纠斜");
      return;
    }

    // 旋转校正
    rotation.value = (rotation.value - skewAngle + 360) % 360;
    drawImage();
    ElMessage.success(`已自动纠斜，旋转校正角度: ${-skewAngle.toFixed(2)}°`);
  };
  img.src = originalImageSrc.value;
};

// 关闭纠斜工具栏
const closeDeskewBar = () => {
  showDeskewBar.value = false;
};

// 文字实时预览样式
const textPreviewStyle = computed(() => {
  return {
    position: "absolute",
    left: textPos.value.x + "px",
    top: textPos.value.y + "px",
    color: textColor.value,
    fontFamily: textFont.value,
    fontSize: textFontSize.value + "px",
    fontWeight: textBold.value ? "bold" : "normal",
    fontStyle: textItalic.value ? "italic" : "normal",
    textDecoration: textUnderline.value ? "underline" : "none",
    cursor: "move",
    zIndex: 99,
    userSelect: "none",
    pointerEvents: showTextBar.value ? "auto" : "none",
    background: "rgba(0,0,0,0.1)",
    padding: "2px 8px",
    borderRadius: "4px",
    border: "1px dashed #5cbfff",
    display: showTextBar.value && textInput.value ? "block" : "none"
  };
});

// 拖动文字预览
const startTextPreviewDrag = e => {
  if (!showTextBar.value) return;
  textPreviewDragging.value = true;
  textPreviewOffset.value = {
    x: e.clientX - textPos.value.x,
    y: e.clientY - textPos.value.y
  };
  document.addEventListener("mousemove", dragTextPreview);
  document.addEventListener("mouseup", stopTextPreviewDrag);
};
const dragTextPreview = e => {
  if (!textPreviewDragging.value) return;
  textPos.value.x = e.clientX - textPreviewOffset.value.x;
  textPos.value.y = e.clientY - textPreviewOffset.value.y;
};
const stopTextPreviewDrag = () => {
  textPreviewDragging.value = false;
  document.removeEventListener("mousemove", dragTextPreview);
  document.removeEventListener("mouseup", stopTextPreviewDrag);
};

// 添加文字到图片
const addTextToImage = () => {
  if (!ctx.value || !canvas.value || !textInput.value) return;
  ctx.value.save();
  let fontStr = "";
  if (textItalic.value) fontStr += "italic ";
  if (textBold.value) fontStr += "bold ";
  fontStr += textFontSize.value + "px " + textFont.value;
  ctx.value.font = fontStr;
  ctx.value.fillStyle = textColor.value;
  ctx.value.textBaseline = "top";
  ctx.value.fillText(textInput.value, textPos.value.x, textPos.value.y);
  // 下划线
  if (textUnderline.value) {
    const textWidth = ctx.value.measureText(textInput.value).width;
    ctx.value.strokeStyle = textColor.value;
    ctx.value.lineWidth = 2;
    ctx.value.beginPath();
    ctx.value.moveTo(textPos.value.x, textPos.value.y + textFontSize.value);
    ctx.value.lineTo(textPos.value.x + textWidth, textPos.value.y + textFontSize.value);
    ctx.value.stroke();
  }
  ctx.value.restore();
  ElMessage.success("文字已添加到图片");
  showTextBar.value = false;
  textInput.value = "";
};
const resetTextInput = () => {
  textInput.value = "";
  textFont.value = "Arial";
  textFontSize.value = 36;
  textColor.value = "#ffffff";
  textBold.value = false;
  textItalic.value = false;
  textUnderline.value = false;
  textPos.value = { x: 200, y: 200 };
};
const closeTextBar = () => {
  showTextBar.value = false;
};
</script>

<style scoped>
.app-container {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 20px;
  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  color: #ffffff;
  background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
}
.app-header {
  padding: 20px 0;
  text-align: center;
}
.app-header h1 {
  margin-bottom: 15px;
  font-size: 28px;
  color: transparent;
  background: linear-gradient(to right, #7ee8fa, #80ff72);
  background-clip: text;
}
.main-content {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.canvas-section {
  position: relative;
  padding: 20px;
  background: #051120;
  border-radius: 10px;
  box-shadow: 0 5px 15px rgb(0 0 0 / 50%);
}
.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
}
.action-buttons {
  margin-left: 15px;
}
.canvas-container {
  position: relative;
  height: 600px;
  overflow: hidden;
  background: #001220;
  border: 1px solid rgb(100 180 255 / 30%);
  border-radius: 8px;
}
canvas {
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
}
.canvas-overlay {
  position: absolute;
  bottom: 15px;
  left: 15px;
  padding: 8px 15px;
  font-size: 14px;
  color: #70c7ff;
  background: rgb(5 15 30 / 80%);
  border: 1px solid rgb(100 200 255 / 30%);
  border-radius: 20px;
  backdrop-filter: blur(5px);
}
.rotation-indicator {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 20;
  width: 80px;
  height: 80px;
}
.rotation-angle {
  font-size: 16px;
  font-weight: bold;
  color: #ffffff;
  text-shadow: 0 0 3px rgb(0 0 0 / 70%);
}
.crop-box {
  position: absolute;
  z-index: 10;
  cursor: move;
  border: 2px dashed #42b983;
  box-shadow: 0 0 0 2000px rgb(0 0 0 / 40%);
}
.crop-handle {
  position: absolute;
  width: 12px;
  height: 12px;
  background: #42b983;
  border-radius: 50%;
}
.crop-handle.top-left {
  top: -6px;
  left: -6px;
  cursor: nwse-resize;
}
.crop-handle.top-right {
  top: -6px;
  right: -6px;
  cursor: nesw-resize;
}
.crop-handle.bottom-left {
  bottom: -6px;
  left: -6px;
  cursor: nesw-resize;
}
.crop-handle.bottom-right {
  right: -6px;
  bottom: -6px;
  cursor: nwse-resize;
}
.preview-section {
  padding: 20px;
  background: rgb(15 30 60 / 80%);
  border-radius: 10px;
  box-shadow: 0 4px 15px rgb(0 0 0 / 40%);
}
.preview-container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}
.preview-placeholder {
  grid-column: span 2;
  padding: 40px 0;
}
.original-preview,
.cropped-preview {
  padding: 15px;
  text-align: center;
  background: rgb(10 25 45 / 60%);
  border-radius: 8px;
}
.preview-image {
  max-width: 100%;
  max-height: 250px;
  margin: 10px 0;
  border: 1px solid rgb(100 180 255 / 30%);
  border-radius: 6px;
}
.image-info {
  font-size: 14px;
  color: #89c2f5;
}
.undo-hint {
  position: absolute;
  top: 20px;
  left: 50%;
  z-index: 30;
  width: 300px;
  transform: translateX(-50%);
}

/* 新增样式 */
.spot-marker {
  position: absolute;
  z-index: 15;
  width: 20px;
  height: 20px;
  pointer-events: none;
  background: rgb(255 0 0 / 50%);
  border-radius: 50%;
  transform: translate(-50%, -50%);
}
.image-stack {
  padding: 15px;
  margin-top: 30px;
  background: rgb(10 25 45 / 60%);
  border-radius: 8px;
}
.stack-container {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
  margin: 15px 0;
}
.stack-item {
  display: flex;
  align-items: center;
  width: 300px;
  padding: 10px;
  background: rgb(15 35 65 / 70%);
  border-radius: 8px;
}
.stack-thumb {
  width: 60px;
  height: 60px;
  margin-right: 15px;
  border-radius: 5px;
  object-fit: cover;
}
.stack-info {
  flex: 1;
}
.stack-actions {
  margin-left: 10px;
}
.instructions {
  position: absolute;
  top: 20px;
  left: 20px;
  z-index: 100;
  width: 300px;
}
.instruction-item {
  margin-bottom: 20px;
}
.instruction-item h3 {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
  color: #5cbfff;
}
.instruction-item p {
  padding-left: 25px;
  margin: 5px 0;
  color: #c0e5ff;
}
.stitch-preview {
  margin-top: 20px;
}
.preview-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-top: 10px;
}
.preview-item {
  position: relative;
  padding: 5px;
  text-align: center;
  border: 1px solid rgb(100 180 255 / 30%);
  border-radius: 6px;
}
.preview-thumb {
  width: 100%;
  height: 80px;
  object-fit: cover;
  border-radius: 4px;
}
.preview-name {
  margin-top: 5px;
  overflow: hidden;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.filter-bar {
  position: absolute;
  top: 70px;
  left: 50%;
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-width: 340px;
  padding: 24px 32px 16px;
  background: rgb(20 30 50 / 98%);
  border: 1px solid #3a7bd5;
  border-radius: 12px;
  box-shadow: 0 4px 16px rgb(0 0 0 / 18%);
  transform: translateX(-50%);
}
.filter-bar .el-slider {
  margin-bottom: 8px;
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.2s;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
.deskew-bar {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.deskew-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}
.text-bar {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 320px;
  padding: 18px 18px 8px;
  background: rgb(20 30 50 / 98%);
  border: 1px solid #3a7bd5;
  border-radius: 12px;
  box-shadow: 0 4px 16px rgb(0 0 0 / 18%);
}
.text-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}
</style>
